<?PHP
/**
  * @brief      陣列處理器
  * @author     Colin
  * @date       2011/09/03
 */
final class Arr
{
    /**
      * @brief    取得任意陣列值,同時允許設定預設直
      * @param    $arr 陣列變數
      * @param    $key 陣列索引名稱
      * @param    $default 如果陣列無此索引鍵時要預設的值
      * @retval    any | null
     */
    const FORMAT_STRING = 'string';
    const FORMAT_INT = 'int';
    const FORMAT_FLOAT = 'float';
    const FORMAT_BOOL = 'bool';
    public static function fetch($arr, $key, $default=NULL, $format='')
    {
        /// 注意 isset(array(key=>NULL)) == false;
        $val = (isset($arr) && isset($arr[$key]))? $arr[$key] : $default;
        switch (strtolower($format))
        {
            case self::FORMAT_STRING:
                $val = (string)$val;
                break;
                
            case self::FORMAT_INT:
                $val = (int)$val;
                break;
                
            case self::FORMAT_FLOAT:
                $val = (float)$val;
                break;
                
            case self::FORMAT_BOOL:
                $val = (bool)$val;
                break;
                
            default:/* empty */
        }
        if (NULL !== $val) {$arr[$key] = $val;}///<逆向操作
        return $val;
    }
    /**
      * @brief 多維陣列遞迴查找
      * @param 第一個參數為source array 其後所有參數為索引鍊
     */
    public static function fetchRecursive()
    {
        $args = func_get_args();
        $retval = current(array_splice($args, 0, 1));
        while ($key = current(array_splice($args, 0, 1)))
        {
            if (is_null($retval = @$retval[$key]))
            {
                break;
            }
        }
        return $retval;
    }
    
    //陣列索引排除
    public static function exclude(array &$arr, $excludes)//:void
    {
        foreach ($arr as $k=>$v)
        {
            if (in_array($k, $excludes))
            {
                unset($arr[$k]);
            }
        }
    }
    
    //迭代器
    public static function each(Closure $callback, array $source)
    {
        foreach ($source as $k=>$v)
        {
           if  (false === $callback($v, $k)) break;
        }
    }
    
    //蒐集符合條件的
    public static function find(Closure $callback, array $source)
    {
        $collection = array();
        self::each(function($v, $k)use($callback, &$collection){
            if ($callback($v, $k))
            {
                if (is_numeric($k))
                {
                    $collection[] = $v;
                }
                else
                {
                    $collection[$k] = $v;
                }
            }
        }, $source);
        return $collection;
    }
    
    public static function inject(Closure $callback, array $source)
    {
        $collection = null;
        self::each(function($v, $k)use($callback, &$collection){
            $collection = $callback($collection, $v, $k);
        }, $source);
        return $collection;
    }
    
    //字串解析,目前僅支援一維陣列
    public static function parse($str)
    {
        $retval = null;
        if (preg_match('/^\[[^\]]*\]$/', $str))
        {
            $retval = array();
            if (preg_match_all('/(?:([^,\s\[\]]+)\s*=\s*)?([^,\[\]]+),?/', $str, $m))
            {
                foreach ($m[2] as $k=>$v)
                {
                    $key = $m[1][$k];
                    if ('' !== $key)
                    {
                        $retval[$key] = $v;
                    }
                    else 
                    {
                        $retval[] = $v;
                    }
                }
            }
        }
        return $retval;
    }
    
    public static function merge_null($arr_main)
    {
        foreach (func_get_args() as $arr)
        {
            foreach ($arr as $k=>$v)
            {
                if (isset($arr_main[$k]))
                {
                    if (gettype($arr_main[$k]) != gettype($v))
                    {
                        throw new Exception("[".  print_r($arr_main[$k], true).' '.gettype($arr_main[$k])."] 與 [".  print_r($v, true).' '.gettype($v)."] 型態不同");
                    }
                    else if (is_array($v))
                    {
                        $arr_main[$k] = self::merge_null($arr_main[$k], $v);
                    }
                    continue;
                }
                $arr_main[$k] = $v;
            }
        }
        return $arr_main;
    }
    public static function merge_min()
    {
        $args = func_get_args();
        array_unshift($args, 'min');
        return call_user_func_array('self::_merge', $args);
    }
    public static function merge_max()
    {
        $args = func_get_args();
        array_unshift($args, 'max');
        return call_user_func_array('self::_merge', $args);
    }
    private static function _merge($method, $arr_main)
    {
        foreach (array_slice(func_get_args(), 1) as $arr)
        {
            foreach ($arr as $k=>$v)
            {
                if (isset($arr_main[$k]))
                {
                    if (gettype($arr_main[$k]) != gettype($v))
                    {
                        throw new Exception("[".  print_r($arr_main[$k], true).' '.gettype($arr_main[$k])."] 與 [".  print_r($v, true).' '.gettype($v)."] 型態不同");
                    }
                    else if (is_array($v))
                    {
                        $arr_main[$k] = self::_merge($method, $arr_main[$k], $v);
                        continue;
                    }
                    $v = $method($arr_main[$k], $v);
                }
                $arr_main[$k] = $v;
            }
        }
        return $arr_main;
    }

    /**
      * @brief 混合資料轉QueryString
      * @param $mixed_data 混合資料
      * @param [string|array] $excludes 排除索引鍵
      * @param $numeric_prefix 第一層索引如果是數字額外加上字首
      * @param $arg_separator 分割字符
     */
    public static function toQueryString(array $mixed_data, $excludes=NULL, $numeric_prefix=NULL, $arg_separator='&')
    {
        if (NULL !== $excludes)
        {
            $excludes = (array)$excludes;
            if (is_array($mixed_data))
            {
                foreach ($excludes as $excludes_key)
                {
                    unset($mixed_data[$excludes_key]);
                }
            }
        }
        return http_build_query($mixed_data, $numeric_prefix, $arg_separator);
    }
    
    ///請注意 字串轉陣列跑大量資料時十分耗效能
    public static function set(&$source, $props, $value)
    {     
        $props = preg_split('/\./', $props);
        $_source = &$source;
        while ($key = current(array_splice($props, 0, 1)))
        {
            $_source[$key] = @$_source[$key]?:array();
            if ( ! is_array($_source[$key]) && ! empty($props))
            {//已經不是陣列,但還沒到目標深度
                return false;
            }
            $_source = &$_source[$key];
        }
        $_source = $value;
        return true;
    }
    public static function get($source, $props)
    {
        $retval = (array)$source;
        if ( ! is_null($props))
        {
            $props = preg_split('/\./', $props);
            while ($key = current(array_splice($props, 0, 1)))
            {
                if (is_null($retval = @$retval[$key]))
                {
                    break;
                }
            }
        }
        return $retval;
    }
    
    /**
      * @brief 快速排序法 (此函式為研究用，請勿拿來作為實作用)
      * @note
      *    @{
            跟PHP原生函式比較 跑1000次 1-9 亂數陣列 
            sort 平均在10ms處理完畢
            quick sort 平均要花上 16X ms
      * @}
     */
    public static function sort(array &$arr)
    {
        self::quickSort($arr, 0, count($arr)-1);
    }
    private static function quickSort(array &$arr, $left, $right)
    {
        if($left < $right)
        { 
            $i = $left; 
            $j = $right + 1;
            $arr_length = count($arr);
            while (true)
            { 
                // 向右找 
                while ($i + 1 < $arr_length && $arr[++$i] < $arr[$left]);  
                // 向左找 
                while ($j -1 > -1 && $arr[--$j] > $arr[$left]);  
                if ($i >= $j) {break;}
                self::swap($arr, $i, $j); 
            } 
            self::swap($arr, $left, $j); 
            self::quickSort($arr, $left, $j-1);   // 對左邊進行遞迴 
            self::quickSort($arr, $j+1, $right);  // 對右邊進行遞迴 
        }
    }
    ///交換陣列內兩個位置的值
    public static function swap(array &$arr, $i, $j)
    {
        $t = $arr[$i]; 
        $arr[$i] = $arr[$j]; 
        $arr[$j] = $t;
    }
}
